package com.bubblegumproject.ogo.action;

import com.bubblegumproject.ogo.component.ActionHandlerComponentInvocation;
import com.bubblegumproject.ogo.component.ComponentProxy;
import com.bubblegumproject.ogo.component.Container;
import org.apache.log4j.Logger;

import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;

/**
 * Simple implementation of the {@link ActionHandlerInvoker} class.
 *
 * @author Azubuko Obele (buko.obele@gmail.com)
 */
public class SimpleActionHandlerInvoker implements ActionHandlerInvoker {

    protected Container container;

    private static final Logger log = Logger.getLogger(SimpleActionHandlerInvoker.class);

    public SimpleActionHandlerInvoker(Container container) {
        assert (container != null);
        this.container = container;
    }

    public boolean invoke(Action action, Object component) {
        assert (action != null && component != null);

        // look for the best ActionHandler method that might execute this method
        ComponentProxy proxy = container.getProxy(component);
        Collection<ActionHandlerComponentInvocation> actionHandlers =
                proxy.getComponentInvocations(ActionHandlerComponentInvocation.class);

        // if it's an TypeParameterizedAction look for a handler whose argument is an action of the same type
        // with the same genericized parameter eg for ParameterizedAction<String> action look for
        // on(ParameterizedAction<String> action) handler
        if (action instanceof TypeParameterizedAction) {
            Class typeParameter = ((TypeParameterizedAction) action).getTypeParameter();
            for (ActionHandlerComponentInvocation h : actionHandlers) {
                if (h.getEventClass().equals(action.getClass())) {

                    // the method must take a single parameter whose parameter is the same class as the Action class
                    // but is parameterized to some concrete type
                    ParameterizedType p = (ParameterizedType) h.getMethod().getGenericParameterTypes()[0];
                    Class parameterizedClass = ((Class) p.getActualTypeArguments()[0]);
                    log.debug("typeParameter=" + typeParameter + " eventClass=" + h.getEventClass() +
                            " actionClass=" + action.getClass() + ", parameterizedClass=" + parameterizedClass + " method=" + h.getMethod());
                    
                    // if the parameteriaed type is parameterized to an equivalent class as the action then we have a
                    // match and can invoke this handler
                    if (typeParameter.isAssignableFrom(parameterizedClass)) {
                        log.debug("typeParameter=" + typeParameter + " parameterizedClass=" + parameterizedClass);
                        h.invoke(component, action);
                        return true;
                    }
                }
            }

            // if we get here we've failed to find an exact match for the TypeParameterizedAction
            return false;
        }

        // handle NamedActions. Look for an ActionHandler that takes a SignalAction with the corresponding name
        if (action instanceof SignalAction) {
            String targetName = ((SignalAction) action).getName();
            for (ActionHandlerComponentInvocation invocation : actionHandlers) {
                if (invocation.getEventClass().equals(SignalAction.class)) {
                    // get the annotations on the first and only annotation parameter
                    Annotation[] parameterAnnotations = invocation.getMethod().getParameterAnnotations()[0];
                    for (Annotation a : parameterAnnotations) {
                        if (a instanceof Signal && ((Signal) a).value().equals(targetName)) {
                            invocation.invoke(component, action);
                            return true;
                        }
                    }
                }
            }
            // if we fall through to here we didn't find a match!
            return false;
        }

        // look for an ActionHandler whose argument matches excactly
        for (ActionHandlerComponentInvocation handler : actionHandlers) {
            if (handler.getEventClass().isInstance(action)) {
                handler.invoke(component, action);
                return true;
            }
        }

        // look for an action handler that is just assignable from the action instance
        for (ActionHandlerComponentInvocation handler : actionHandlers) {
            if (handler.getEventClass().isAssignableFrom(action.getClass())) {
                handler.invoke(component, action);
                return true;
            }
        }


        return false;
    }
}
